﻿/*日志配置器
 * 持有所有的配置实体
 * 
 * 日志标准配置
 *异常判断：
 *一、configuration必须存在
 *二、Global必须存在
 *Formatter和Appender最少有一项；
 *ID不得重复
 *内容中必须有逗号且不在第一位
 *三、Config可以不配
 *Logger的Name不得重复
 *Actions可以不配，默认为所有；配置All为所有；大小写不敏感
 *Appender和formatter可以不配，默认使用default
 *Appedner Params可以不配，默认null;分号隔开
 * 四、以上大小写敏感
 * 
 * Created By WangZg
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;

using Octopus.Logging.Commons;
using Octopus.Logging.Loggers;

namespace Octopus.Logging.Configuration
{
    public class ConfigManager
    {
        #region 配置文件节点编码
        /// <summary>
        /// 配置文件主节点
        /// </summary>
        private static string ndnRoot = "configuration";

        /// <summary>
        /// 格式器节点
        /// </summary>
        private static string ndnFormatter = "Formatter";
        /// <summary>
        /// 适配器节点
        /// </summary>
        private static string ndnAppender = "Appender";
        /// <summary>
        /// 业务日志主节点
        /// </summary>
        private static string ndnBusinessLogs = "Config";
        /// <summary>
        /// 业务日志节点
        /// </summary>
        private static string ndnBusinessLog = "Logger";
        private static string ndnAction = "Actions";
        private static string ndnParams = "Params";
        private static string ndnParam = "Param";

        /*节点属性*/
        private static string ndnID = "ID";
        private static string ndnName = "Name";
        private static string ndnType = "Type";
        private static string ndnValue = "Value";
        private static string ndnSupportMT = "SupportMultipleThreading";
        private static string ndnConfigSource = "ConfigSource";

        //若关联节点未设置，使用default ID
        private static string ndvDefault = "default";
        /// <summary>
        /// 在应用.config中配置的AppSetting属性
        /// </summary>
        private static string ndvSystemConfig = "LoggingConfig";
        
        private IDictionary<string, BusinessLogEntily> logConfigList;
        private IDictionary<string, ComponentEntily> formatters;
        private IDictionary<string, ComponentEntily> appenders;

        private bool _IsLoadSuccess;
        private bool _IsHasLogs;
        private string _WariningInfo;
        private delegate void LoadXml(XmlNode nd);
        #endregion

        #region 公开方法
        /// <summary>
        /// 加载指定目录或路径下的配置文件，并且与默认配置合并后解析
        /// </summary>
        /// <param name="path">指定目录或路径</param>
        public void LoadConfig(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                path = System.Configuration.ConfigurationManager.AppSettings[ndvSystemConfig];             
            }
            if (!string.IsNullOrEmpty(path))
            {
                path = Path.GetFullPath(AppDomain.CurrentDomain.SetupInformation.ApplicationBase) + path;
            }

            string bizConfig = "<other>";
            if (Directory.Exists(path))
            {
                StringBuilder sb = new StringBuilder();
                string[] files = Directory.GetFiles(path, "*.config", SearchOption.TopDirectoryOnly);
                foreach (string s in files)
                {
                    sb.AppendLine(ReadConfig(s));
                }
                bizConfig += sb.ToString();
            }
            else if (File.Exists(path))
            {
                bizConfig += File.ReadAllText(path);
            }
            bizConfig += "</other>";

            XmlDocument xmlDoc = new XmlDocument();
            Stream defaultConfig = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Octopus.Logging.Resource.default.config");
            xmlDoc.Load(defaultConfig);

            if (!string.IsNullOrEmpty(path))
            {
                StringReader sr = new StringReader(bizConfig);
                XmlDocument xmlOther = new XmlDocument();
                xmlOther.Load(sr);

                foreach (XmlNode item in xmlOther.ChildNodes[0].ChildNodes)
                {
                    XmlNode nodeConfig = xmlDoc.ImportNode(item, true);
                    xmlDoc.SelectSingleNode(ndnRoot).AppendChild(nodeConfig);
                }
            }

            BuildConfig(xmlDoc);
        }


       /// <summary>
       /// 根据完整的配置文件构建配置
       /// </summary>
        /// <param name="xmlDoc">完整的配置文件</param>
        private void BuildConfig(XmlDocument xmlDoc)
        {
            try
            {
                XmlNode root = xmlDoc.SelectSingleNode(ndnRoot);
                if (root == null) { throw new LogException(LogException.ConfigRootNull); }

                formatters = new Dictionary<string, ComponentEntily>();
                appenders = new Dictionary<string, ComponentEntily>();

                LoadGlobalConfig(root);
                if (appenders.Count < 1 || formatters.Count < 1) { throw new LogException(LogException.ConfigGlobalEmpty); }

                logConfigList = new Dictionary<string, BusinessLogEntily>();
                LoadBussinessConfig(root);
            }
            catch 
            {
                throw ;
            }
            finally
            {
                xmlDoc = null;
            }

            _IsLoadSuccess = true;
        }

        public BusinessLogEntily GetModuleConfig(string moduleName)
        {
            if (IsExistModule(moduleName))
            {
                return logConfigList[moduleName];
            }
            return null;
        }
        /// <summary>
        /// 有则返回Log配置对象，无则创建默认
        /// </summary>
        /// <param name="loggerName"></param>
        /// <returns></returns>
        public BusinessLogEntily BuildLoggerConfig(string loggerName)
        {
            BusinessLogEntily config = GetModuleConfig(loggerName);
            if (config != null) { return config; }
            else { return BuildDefaultLogEntily(loggerName); }            
        }

        #endregion

        #region 公开属性
        /// <summary>配置是否加载成功过</summary>
        public bool IsLoadSuccess
        {
          get { return _IsLoadSuccess; }
        }
        /// <summary>配置是否加载到业务日志</summary>
        public bool IsHasLogs
        {
            get { return _IsHasLogs; }
        }
        /// <summary>指定模块是否配置日志</summary>
        public bool IsExistModule(string moduleName)
        {
            return IsHasLogs && logConfigList.ContainsKey(moduleName);
        }
        /// <summary>指定模块是否配置日志</summary>
        public string WariningInfo
        {
            get { return _WariningInfo; }
            set { _WariningInfo = value; }
        }
        #endregion

        #region 私有实现

        private FormatterEntily GetGobalFormatter(string id)
        {
            if (formatters.ContainsKey(id))
            {
                return formatters[id] as FormatterEntily;
            }
            else
            {
                LogWarning("未配置ID为" + id + "的Formatter");
                if (formatters.ContainsKey(ndvDefault)) { return formatters[ndvDefault] as FormatterEntily; }
                else return null;
            }
        }
        private AppenderEntily GetGobalAppender(string id)
        {
            if (appenders.ContainsKey(id))
            {
                return appenders[id] as AppenderEntily;
            }
            else
            {
                LogWarning("未配置ID为" + id + "的Appender");
                if (appenders.ContainsKey(ndvDefault)) { return appenders[ndvDefault] as AppenderEntily; }
                else return null;
            }
        }        

        private void LoadConfigSource(XmlNode node, LoadXml delagate)
        {
            if (!string.IsNullOrEmpty(Common.GetXmlNodeAtt(node, ndnConfigSource)) && delagate!=null)
            {
                string sFile =Path.Combine( AppDomain.CurrentDomain.BaseDirectory ,Common.GetXmlNodeAtt(node, ndnConfigSource));
                if (!File.Exists(sFile)) 
                { 
                    LogWarning("外部引用配置不存在"+sFile);
                    return;
                }

                XmlDocument xmldoc = new XmlDocument();
                xmldoc.Load(sFile);
                XmlNode root = xmldoc.SelectSingleNode(ndnRoot);
                if (root == null) { throw new LogException(LogException.ConfigRootNull); }

                delagate(root);
                xmldoc = null;
            }

            return;
        }

        private void LoadGlobalConfig(XmlNode root)
        {
            //XmlNode node = root.SelectSingleNode(ndnRoot);
            //if (node == null) { throw new LogException(LogException.ConfigGlobalNull); }

            //LoadConfigSource(node, LoadGlobalConfig);

            LoadGlobalItems(root.SelectNodes(ndnFormatter), formatters, FormatterEntily.Create);
            LoadGlobalItems(root.SelectNodes(ndnAppender), appenders, AppenderEntily.Create);
        }
        private void LoadGlobalItems(XmlNodeList list, IDictionary<string, ComponentEntily> container, ComponentEntily.CreateComponentConfigEntily createHandle)
        {
            foreach (XmlNode item in list)
            {
                string sID = Common.GetXmlNodeAtt(item, ndnID);
                if (string.IsNullOrEmpty(sID)) { throw new LogException(LogException.ConfigIDNull, item.Name); }
                if (container.ContainsKey(sID)) { throw new LogException(LogException.ConfigItemDuplication, sID); }

                string sType = Common.GetXmlNodeAtt(item, ndnType);
                if (string.IsNullOrEmpty(sType) || sType.IndexOf(',') < 1) { throw new LogException(LogException.ConfigAssemblyDeformity, sType); }

                ComponentEntily entily = createHandle();
                entily.ID = sID;
                entily.Type = sType;
                entily.Params = LoadParams(item);
                entily.SupportMultipleThreading = (bool)Common.ConvertStringTo(typeof(bool), Common.GetXmlNodeAtt(item, ndnSupportMT, "false"));

                container.Add(sID, entily);
            }
        }
        /// <summary>
        /// 获取扩展组件配置参数
        /// </summary>
        /// <param name="item">Appender或Formatter节点</param>
        /// <returns></returns>
        private Dictionary<string, string> LoadParams(XmlNode item)
        {
            if (item == null) { return new Dictionary<string,string>(); }

            XmlNodeList paramNodes = item.SelectNodes(ndnParams + "/" + ndnParam);
            if (paramNodes != null && paramNodes.Count > 0)
            {
                Dictionary<string, string> paramList = new Dictionary<string, string>(paramNodes.Count);
                foreach (XmlNode param in paramNodes)
                {
                    string paramName = Common.GetXmlNodeAtt(param, ndnName);
                    if (string.IsNullOrEmpty(paramName)) { throw new LogException(LogException.ConfigAssemblyParamNameNull); }
                    if (paramList.ContainsKey(paramName)) { throw new LogException(LogException.ConfigAssemblyParamDuplication, paramName); }

                    paramList.Add(paramName, Common.GetXmlNodeAtt(param, ndnValue, string.Empty));
                }
                return paramList;
            }
            else
            {
                return null;
            }             
        }
        private BusinessLogEntily BuildDefaultLogEntily(string loggerName)
        {
            BusinessLogEntily entily = new BusinessLogEntily() { IsHaveConfig=false};
            entily.Name = loggerName;
            entily.Actions = ActionStateHelper.ConvertAllAction();
            entily.Appender = GetGobalAppender(ndvDefault);
            entily.Format = GetGobalFormatter(ndvDefault);
            return entily;
        }
        private void SetAppender(BusinessLogEntily entily,XmlNode ndInner)
        {
            AppenderEntily appender = GetGobalAppender(entily.Appender.ID);
            if (appender != null)
            {
                entily.Appender.Type = appender.Type;
                entily.Appender.Params = LoadParams(ndInner);
                Common.JoinDictionary(appender.Params, entily.Appender.Params, false, null);
                entily.Appender.SupportMultipleThreading = appender.SupportMultipleThreading;
            } 
        }
        private void SetFormatter(BusinessLogEntily entily,XmlNode ndInner)
        {
            FormatterEntily formatter = GetGobalFormatter(entily.Format.ID);
            if (formatter != null)
            {
                entily.Format.Type = formatter.Type;
                entily.Format.Params = LoadParams(ndInner);
                Common.JoinDictionary(formatter.Params, entily.Format.Params, false, null);
                entily.Format.SupportMultipleThreading = formatter.SupportMultipleThreading;
            }
        }

        private void LoadBussinessConfig(XmlNode root)
        {
            XmlNode node = root;    //.SelectSingleNode(ndnBusinessLogs);
            if (node == null) { return; }

            LoadConfigSource(node, LoadBussinessConfig);

            foreach (XmlNode item in node.SelectNodes(ndnBusinessLog))
            {
                string name = Common.GetXmlNodeAtt(item, ndnName);
                if (string.IsNullOrEmpty(name)) { throw new LogException(LogException.ConfigNameNull); }
                if (logConfigList.ContainsKey(name)) { throw new LogException(LogException.ConfigItemDuplication, name); }
                
                BusinessLogEntily entily = new BusinessLogEntily();
                entily.Name = name;
   
                XmlNode ndInner = item.SelectSingleNode(ndnAction);
                if (ndInner != null)
                {
                    string sActions = Common.GetXmlNodeValue(ndInner);
                    entily.Actions = string.IsNullOrEmpty(sActions) ? string.Empty : (sActions.ToLower() == "all" ? ActionStateHelper.ConvertAllAction() : sActions);
                    entily.Actions = entily.Actions.ToLower();
                }
                else
                {
                    entily.Actions = ActionStateHelper.ConvertAllAction();
                }

                ndInner = item.SelectSingleNode(ndnAppender);
                entily.Appender = new AppenderEntily();
                if (ndInner != null)
                {
                    entily.Appender.ID = Common.GetXmlNodeAtt(ndInner, ndnID, ndvDefault);                  
                }
                else
                {
                    entily.Appender.ID = ndvDefault;
                }
                SetAppender(entily, ndInner);                

                ndInner = item.SelectSingleNode(ndnFormatter);
                entily.Format = new FormatterEntily();
                if (ndInner != null)
                {
                    entily.Format.ID = Common.GetXmlNodeValue(ndInner, ndvDefault);
                }
                else
                {
                    entily.Format.ID = ndvDefault;
                }
                SetFormatter(entily, ndInner);                

                logConfigList.Add(entily.Name, entily);
            }
            _IsHasLogs = true;
        }

        private string ReadConfig(string file)
        {
            return File.ReadAllText(file);
        }

        private void LogWarning(string message)
        {
            _WariningInfo += message + ";";
        }
        #endregion
    }
}
